// SPDX-FileCopyrightText: Adam Evyčędo
//
// SPDX-License-Identifier: AGPL-3.0-or-later

package traffic

import (
	"fmt"
	"strings"
	"time"

	"github.com/dhconnelly/rtreego"
	"golang.org/x/text/language"
)

type DeparturesType uint8

const (
	DEPARTURES_HYBRID DeparturesType = iota
	DEPARTURES_FULL
)

const ZWJ = "\u200d"

func LuaAlertToAlert(luaAlert AlertLua) (Alert, error) {
	resultAlert := Alert{
		TimeRanges:   make([][2]time.Time, len(luaAlert.TimeRanges)),
		Headers:      map[language.Tag]string{},
		Descriptions: map[language.Tag]string{},
		URLs:         map[language.Tag]string{},
		Cause:        AlertCause(luaAlert.Cause),
		Effect:       AlertEffect(luaAlert.Effect),
	}
	for i, r := range luaAlert.TimeRanges {
		start, err := time.Parse("2006-01-02T15:04:05.999Z07:00", r.StartDate)
		if err != nil {
			return Alert{}, fmt.Errorf("error parsing time for range %s: %w", r.StartDate, err)
		}
		end, err := time.Parse("2006-01-02T15:04:05.999Z07:00", r.EndDate)
		if err != nil {
			return Alert{}, fmt.Errorf("error parsing time for range %s: %w", r.EndDate, err)
		}
		resultAlert.TimeRanges[i] = [2]time.Time{start, end}
	}
	for l, header := range luaAlert.Headers {
		tag, err := language.Parse(l)
		if err != nil {
			return Alert{}, fmt.Errorf("error parsing tag %s: %w", l, err)
		}
		resultAlert.Headers[tag] = header
	}
	for l, description := range luaAlert.Descriptions {
		tag, err := language.Parse(l)
		if err != nil {
			return Alert{}, fmt.Errorf("error parsing tag %s: %w", l, err)
		}
		resultAlert.Descriptions[tag] = description
	}
	for l, url := range luaAlert.Urls {
		tag, err := language.Parse(l)
		if err != nil {
			return Alert{}, fmt.Errorf("error parsing tag %s: %w", l, err)
		}
		resultAlert.URLs[tag] = url
	}
	return resultAlert, nil
}

func (v VehicleStatus) Location() Position {
	return Position{
		Lat: v.Latitude,
		Lon: v.Longitude,
	}
}

func (k LineType) Value() uint {
	return uint(k)
}

func (d Direction) Value() uint {
	return uint(d)
}

type DepartureRealtime struct {
	Time      time.Time
	Departure Departure
	Headsign  string
	LineID    string
	Order     StopOrder
	Update    Update
	Alerts    []SpecificAlert
}

func (d DepartureRealtime) WithUpdate(update Update) DepartureRealtime {
	d.Update = update
	return d
}

func (d DepartureRealtime) WithAlerts(alerts []Alert, languages []language.Tag) DepartureRealtime {
	d.Alerts = selectSpecificAlerts(alerts, languages)
	return d
}

func selectSpecificAlerts(alerts []Alert, languages []language.Tag) []SpecificAlert {
	now := time.Now()
	validAlerts := []SpecificAlert{}
	for _, alert := range alerts {
		timeValid := len(alert.TimeRanges) == 0
		for _, timeRange := range alert.TimeRanges {
			if now.After(timeRange[0]) && now.Before(timeRange[1]) {
				timeValid = true
				break
			}
		}
		if !timeValid {
			continue
		}

		headerLanguages := make([]language.Tag, len(alert.Headers))
		i := 0
		for l := range alert.Headers {
			headerLanguages[i] = l
			i++
		}
		headerMatcher := language.NewMatcher(headerLanguages)
		ht, _, _ := headerMatcher.Match(languages...)
		descriptionLanguages := make([]language.Tag, len(alert.Descriptions))
		i = 0
		for l := range alert.Descriptions {
			descriptionLanguages[i] = l
			i++
		}
		descriptionMatcher := language.NewMatcher(descriptionLanguages)
		dt, _, _ := descriptionMatcher.Match(languages...)
		URLLanguages := make([]language.Tag, len(alert.URLs))
		i = 0
		for l := range alert.URLs {
			URLLanguages[i] = l
			i++
		}
		URLMatcher := language.NewMatcher(URLLanguages)
		ut, _, _ := URLMatcher.Match(languages...)

		validAlerts = append(validAlerts, SpecificAlert{
			Header:      alert.Headers[ht],
			Description: alert.Descriptions[dt],
			URL:         alert.URLs[ut],
			Cause:       alert.Cause,
			Effect:      alert.Effect,
		})
	}
	return validAlerts
}

func (g LineGraph) LastNodes() []int {
	lastNodes := []int{}
	for i, nextNodes := range g.NextNodes {
		for _, node := range nextNodes {
			if node == -1 {
				lastNodes = append(lastNodes, i)
				break
			}
		}
	}
	return lastNodes
}

type LineStub struct {
	Name   string
	Colour string
	Type   string
}

func (l Line) DisplayName() string {
	return l.Name
}

func (l Line) IsItem() {}

type Shape struct { // todo(BAF11)
	Points []Position
}

type Queryable interface {
	IsItem()
	DisplayName() string
}

type Locatable interface {
	Location() Position
}

func (s Stop) DisplayName() string {
	return s.Name
}

func (s Stop) Location() Position {
	return s.Position
}

func (s Stop) Bounds() *rtreego.Rect {
	rect, err := rtreego.NewRectFromPoints(
		rtreego.Point{s.Position.Lat, s.Position.Lon},
		rtreego.Point{s.Position.Lat, s.Position.Lon},
	)
	if err != nil {
		panic(err.Error())
	}
	return rect
}

func (s Stop) IsItem() {}

type TimedStopStub struct {
	StopStub
	Time uint
}

type StopStub struct {
	Code     string
	Name     string
	NodeName string
	Zone     string
	OnDemand bool
}

type Validity string // 20060102_20060102
func (v Validity) Start() string {
	return strings.Split(string(v), "_")[0]
}
func (v Validity) End() string {
	return strings.Split(string(v), "_")[1]
}

type FeedCodeIndex map[Validity]CodeIndex
type GlobalCodeIndex map[string]FeedCodeIndex

type NameIndex []NameOffset
type FeedNameIndex map[Validity]NameIndex
type GlobalNameIndex map[string]FeedNameIndex

func (ix NameIndex) String(i int) string {
	return ix[i].Name
}

func (ix NameIndex) Len() int {
	return len(ix)
}

type FeedCalendar map[Validity][]Schedule
type GlobalCalendar map[string]FeedCalendar

type Vehicles map[string]Vehicle
type FeedVehicles map[Validity]Vehicles
type GlobalVehicles map[string]FeedVehicles

type FeedPositionIndex map[Validity]*rtreego.Rtree
type GlobalPositionIndex map[string]FeedPositionIndex

type Version struct {
	Link      string
	ValidFrom time.Time
	ValidTill time.Time
}

func (v Version) String() string {
	return v.ValidFrom.Format(ValidityFormat) + "_" + v.ValidTill.Format(ValidityFormat)
}

type GlobalVersions map[string][]Version

var DateFormat string = "2006-01-02"
var DateTimeFormat string = "2006-01-02T15:04:05-07:00"
var ValidityFormat string = "20060102"
var ValidityFormatExtended string = "20060102150405"

type Traffic struct {
	CodeIndexes     GlobalCodeIndex
	NameIndexes     GlobalNameIndex
	LineIdIndexes   GlobalCodeIndex
	LineIndexes     GlobalNameIndex
	TripIndexes     GlobalNameIndex
	PositionIndexes GlobalPositionIndex
	Versions        GlobalVersions
	Calendars       GlobalCalendar
	Vehicles        GlobalVehicles
	Feeds           map[string]Feed
	FeedInfos       map[Validity]map[string]FeedInfo
}

type Context struct {
	DataHome string
	FeedID   string
	Version  Validity
}

const (
	INDEX_STOP_CODE = "ix_stop_codes"
	INDEX_STOPS     = "ix_stop_names"
	INDEX_TRIPS     = "ix_trips"
	INDEX_LINES     = "ix_lines"
	INDEX_LINE_CODE = "ix_line_codes"
)

const (
	FILE_INDEX_DB        = "index.db"
	FILE_INDEX_STOP_CODE = "ix_stop_codes.bare"
	FILE_INDEX_STOPS     = "ix_stop_names.bare"
	FILE_INDEX_TRIPS     = "ix_trips.bare"
	FILE_INDEX_LINE      = "ix_lines.bare"
	FILE_INDEX_LINE_CODE = "ix_line_codes.bare"
)
